(function(jQuery) {
	if (typeof jQuery == "undefined") {
		throw "Unable to load Ext, jQuery not found.";
	}
	jQuery.extend({
				isEmpty : function(v, allowBlank) {
					return v === null || v === undefined || ((jQuery.isArray(v) && !v.length)) || (!allowBlank ? v === '' : false);
				},
				isDate : function(v) {
					return toString.apply(v) === '[object Date]';
				},
				isObject : function(v) {
					return !!v && Object.prototype.toString.call(v) === '[object Object]';
				},
				isDefined : function(v) {
					return typeof v !== 'undefined';
				},
				/**
				 * Takes an object and converts it to an encoded URL. e.g.
				 * jQuery.urlEncode({foo: 1, bar: 2}); would return
				 * "foo=1&bar=2". Optionally, property values can be arrays,
				 * instead of keys and the resulting string that's returned will
				 * contain a name/value pair for each array value.
				 * 
				 * @param {Object}
				 *            o
				 * @param {String}
				 *            pre (optional) A prefix to add to the url encoded
				 *            string
				 * @return {String}
				 */
				urlEncode : function(o, pre) {
					var empty, buf = [], e = encodeURIComponent;

					jQuery.each(o, function(key, item) {
								empty = jQuery.isEmpty(item);
								jQuery.each(empty ? key : item, function(i, val) {
											buf.push('&', e(key), '=', (!jQuery.isEmpty(val) && (val != key || !empty)) ? (jQuery.isDate(val) ? jQuery.encode(val)
															.replace(/"/g, '') : e(val)) : '');
										});
							});
					if (!pre) {
						buf.shift();
						pre = '';
					}
					return pre + buf.join('');
				},
				/**
				 * Takes an encoded URL and and converts it to an object.
				 * Example:
				 * 
				 * <pre><code>
				 * jQuery.urlDecode(&quot;foo=1&amp;bar=2&quot;); // returns {foo: &quot;1&quot;, bar: &quot;2&quot;}
				 * jQuery.urlDecode(&quot;foo=1&amp;bar=2&amp;bar=3&amp;bar=4&quot;, false); // returns {foo: &quot;1&quot;, bar: [&quot;2&quot;, &quot;3&quot;, &quot;4&quot;]}
				 * </code></pre>
				 * 
				 * @param {String}
				 *            string
				 * @param {Boolean}
				 *            overwrite (optional) Items of the same name will
				 *            overwrite previous values instead of creating an
				 *            an array (Defaults to false).
				 * @return {Object} A literal with members
				 */
				urlDecode : function(string, overwrite) {
					if (jQuery.isEmpty(string)) {
						return {};
					}
					var obj = {}, pairs = string.split('&'), d = decodeURIComponent, name, value;
					jQuery.each(pairs, function(i, pair) {
								pair = pair.split('=');
								name = d(pair[0]);
								value = d(pair[1]);
								obj[name] = overwrite || !obj[name] ? value : [].concat(obj[name]).concat(value);
							});
					return obj;
				},

				/**
				 * Appends content to the query string of a URL, handling logic
				 * for whether to place a question mark or ampersand.
				 * 
				 * @param {String}
				 *            url The URL to append to.
				 * @param {String}
				 *            s The content to append to the URL.
				 * @return (String) The resulting URL
				 */
				urlAppend : function(url, s) {
					if (!jQuery.isEmpty(s)) {
						return url + (url.indexOf('?') === -1 ? '?' : '&') + s;
					}
					return url;
				},
				/**
				 * Allows you to define a tokenized string and pass an arbitrary
				 * number of arguments to replace the tokens. Each token must be
				 * unique, and must increment in the format {0}, {1}, etc.
				 * Example usage:
				 * 
				 * <pre><code>
				 * var cls = 'my-class', text = 'Some text';
				 * var s = String.format('&lt;div class=&quot;{0}&quot;&gt;{1}&lt;/div&gt;', cls, text);
				 * // s now contains the string: '&lt;div class=&quot;my-class&quot;&gt;Some text&lt;/div&gt;'
				 * </code></pre>
				 * 
				 * @param {String}
				 *            string The tokenized string to be formatted
				 * @param {String}
				 *            value1 The value to replace token {0}
				 * @param {String}
				 *            value2 Etc...
				 * @return {String} The formatted string
				 * @static
				 */
				stringFormat : function(format) {
					var args = arguments;
					var s = format.replace(/\{(\d+)\}/g, function(m, i) {
								return args[parseInt(i) + 1];
							});
					return s;
				},
				toJSONString : function(object) {
					var type = typeof object;
					if ('object' == type) {
						if (Array == object.constructor)
							type = 'array';
						else if (RegExp == object.constructor)
							type = 'regexp';
						else
							type = 'object';
					}
					switch (type) {
						case 'undefined' :
						case 'unknown' :
							return;
							break;
						case 'function' :
						case 'boolean' :
						case 'regexp' :
							return object.toString();
							break;
						case 'number' :
							return isFinite(object) ? object.toString() : 'null';
							break;
						case 'string' :
							return '"' + object.replace(/(\\|\")/g, "\\$1").replace(/\n|\r|\t/g, function() {
										var a = arguments[0];
										return (a == '\n') ? '\\n' : (a == '\r') ? '\\r' : (a == '\t') ? '\\t' : ""
									}) + '"';
							break;
						case 'object' :
							if (object === null)
								return 'null';
							var results = [];
							for (var property in object) {
								var value = jQuery.toJSONString(object[property]);
								if (value !== undefined)
									results.push(jQuery.toJSONString(property) + ':' + value);
							}
							return '{' + results.join(',') + '}';
							break;
						case 'array' :
							var results = [];
							for (var i = 0; i < object.length; i++) {
								var value = jQuery.toJSONString(object[i]);
								if (value !== undefined)
									results.push(value);
							}
							return '[' + results.join(',') + ']';
							break;
					}
				}
			});

	jQuery.extend(Array.prototype, {
				/**
				 * Checks whether or not the specified object exists in the
				 * array.
				 * 
				 * @param {Object}
				 *            o The object to check for
				 * @param {Number}
				 *            from (Optional) The index at which to begin the
				 *            search
				 * @return {Number} The index of o in the array (or -1 if it is
				 *         not found)
				 */
				indexOf : function(o, from) {
					var len = this.length;
					from = from || 0;
					from += (from < 0) ? len : 0;
					for (; from < len; ++from) {
						if (this[from] === o) {
							return from;
						}
					}
					return -1;
				},

				/**
				 * Removes the specified object from the array. If the object is
				 * not found nothing happens.
				 * 
				 * @param {Object}
				 *            o The object to remove
				 * @return {Array} this array
				 */
				remove : function(o) {
					var index = this.indexOf(o);
					if (index != -1) {
						this.splice(index, 1);
					}
					return this;
				},
				/**
				 * 去除重复数据
				 * field 可选类属性
				 */
				unique : function(field) {
					var setTemp = {}, hasField = typeof(field) != 'undefined';
					for (var i = this.length - 1; i >= 0; i--) {
						var obj = this[i], cacheKey = hasField ? obj[field] : obj;
						if (cacheKey in setTemp) {
							this.splice(i, 1);
							continue;
						} else {
							setTemp[cacheKey] = null;
						}
					}
				}
			});

	var TaskRunner = function(interval) {
		interval = interval || 10;
		var tasks = new Array(), removeQueue = new Array(), id = 0, running = false,

		// private
		stopThread = function() {
			running = false;
			clearInterval(id);
			id = 0;
		},

		// private
		startThread = function() {
			if (!running) {
				running = true;
				id = setInterval(runTasks, interval);
			}
		},

		// private
		removeTask = function(t) {
			removeQueue.push(t);
			if (t.onStop) {
				t.onStop.apply(t.scope || t);
			}
		},

		// private
		runTasks = function() {
			var rqLen = removeQueue.length, now = new Date().getTime();

			if (rqLen > 0) {
				for (var i = 0; i < rqLen; i++) {
					tasks.remove(removeQueue[i]);
				}
				removeQueue = [];
				if (tasks.length < 1) {
					stopThread();
					return;
				}
			}
			for (var i = 0, t, itime, rt, len = tasks.length; i < len; ++i) {
				t = tasks[i];
				itime = now - t.taskRunTime;
				if (t.interval <= itime) {
					rt = t.run.apply(t.scope || t, t.args || [++t.taskRunCount]);
					t.taskRunTime = now;
					if (rt === false || t.taskRunCount === t.repeat) {
						removeTask(t);
						return;
					}
				}
				if (t.duration && t.duration <= (now - t.taskStartTime)) {
					removeTask(t);
				}
			}
		};
		this.start = function(task) {
			tasks.push(task);
			task.taskStartTime = new Date().getTime();
			task.taskRunTime = 0;
			task.taskRunCount = 0;
			startThread();
			return task;
		};
		this.stop = function(task) {
			removeTask(task);
			return task;
		};
		this.stopAll = function() {
			stopThread();
			for (var i = 0, len = tasks.length; i < len; i++) {
				if (tasks[i].onStop) {
					tasks[i].onStop();
				}
			}
			tasks = [];
			removeQueue = [];
		};
	}
	jQuery.TaskMgr = new TaskRunner();

})(jQuery);

(function(jQuery) {
	if (typeof jQuery == "undefined") {
		throw "Unable to load Ext, jQuery not found.";
	}
	var draw = function draw(id) {
		this.elem = jQuery("#" + id);
		if (this.elem.html().length > 0) {
			this.elem.html("");
		}
		this.drawLine = function(x0, y0, x1, y1, color, arrow) {
			if (!color)
				var color = "red";
			var rs = "";
			if (y0 == y1) {
				// 画横线
				if (x0 > x1) {
					var t = x0;
					x0 = x1;
					x1 = t;
				}
				rs = "<span style='top:" + y0 + "px;left:" + x0 + "px;position:absolute;font-size:1px;background-color:" + color + ";height:2px; width:" + Math.abs(x1 - x0)
						+ "px'></span>";
			} else if (x0 == x1) {
				// 画竖线
				if (y0 > y1) {
					var t = y0;
					y0 = y1;
					y1 = t;
				}
				rs = "<span style='top:" + y0 + "px;left:" + x0 + "px;position:absolute;font-size:1px;background-color:" + color + ";width:2px;height:" + Math.abs(y1 - y0)
						+ "px'></span>";
			} else {
				var lx = x1 - x0;
				var ly = y1 - y0;
				var l = Math.sqrt(lx * lx + ly * ly);
				rs = new Array();
				for (var i = 0; i < l; i += 1) {
					var p = i / l;
					var px = parseInt(x0 + lx * p);
					var py = parseInt(y0 + ly * p);
					rs[rs.length] = "<span style='top:" + py + "px;left:" + px + "px;height:2px;width:2px;position:absolute;font-size:1px;background-color:" + color + "'></span>";
				}
				rs = rs.join("");
			}
			this.elem.append(rs);
			if (arrow) {
				this.drawArrow(x0, y0, x1, y1, color);
			}
		}

		function Point(x, y) {
			this.x = x;
			this.y = y;
		}

		function Rectang(x, y, w, h) {
			this.x = x;
			this.y = y;
			this.width = w;
			this.height = h;

			this.grow = function(h, v) {
				var x0 = this.x, y0 = this.y, x1 = this.width, y1 = this.height;
				// x1 += x0;
				// y1 += y0;

				x0 -= h;
				y0 -= v;
				x1 += 2 * h;
				y1 += 2 * v;

				this.x = x0;
				this.y = y0;
				this.width = x1;
				this.height = y1;
			};
			this.getCenterX = function() {
				return this.x + this.width / 2;
			};
			this.getCenterY = function() {
				return this.y + this.height / 2;
			}
		}

		function getSlope(x1, y1, x2, y2) {
			return (y2 - y1) / (x2 - x1);
		}
		function getYIntercep(x1, y1, x2, y2) {
			return y1 - x1 * getSlope(x1, y1, x2, y2);
		}

		/**
		 * 获得矩形中心p0与p1的线段和矩形的交点
		 * 
		 * @param {}
		 *            rect
		 * @param {}
		 *            p1
		 * @param {}
		 *            grow
		 * @return {}
		 */
		function getRectangleLineCrossPoint(rect, p1, grow) {
			rect.grow(grow, grow);
			var p0 = new Point(rect.getCenterX(), rect.getCenterY());

			if (p1.x == p0.x) {
				if (p1.y < p0.y) {
					return new Point(p0.x, rect.y);
				}
				return new Point(p0.x, rect.y + rect.height);
			}

			if (p1.y == p0.y) {
				if (p1.x < p0.x) {
					return new Point(rect.x, p0.y);
				}
				return new Point(rect.x + rect.width, p0.y);
			}

			var slope = getSlope(p0.x, p0.y, rect.x, rect.y);
			var slopeLine = getSlope(p0.x, p0.y, p1.x, p1.y);
			var yIntercep = getYIntercep(p0.x, p0.y, p1.x, p1.y);

			if (Math.abs(slopeLine) > slope - 0.01) {
				if (p1.y < rect.y) {
					return new Point(((rect.y - yIntercep) / slopeLine), rect.y);
				} else {
					return new Point(((rect.y + rect.height - yIntercep) / slopeLine), rect.y + rect.height);
				}
			}
			if (p1.x < rect.x) {
				return new Point(rect.x, (slopeLine * rect.x + yIntercep));
			} else {
				return new Point(rect.x + rect.width, (slopeLine * (rect.x + rect.width) + yIntercep));
			}
		}

		this.drawArrow = function(x1, y1, x2, y2, color) {
			var len = 6.0;
			var slopy = Math.atan2(y2 - y1, x2 - x1);
			var cosy = Math.cos(slopy);
			var siny = Math.sin(slopy);
			var xPoints = [0, x2, 0];
			var yPoints = [0, y2, 0];
			var a = len * siny, b = len * cosy;
			var c = len / 2.0 * siny, d = len / 2.0 * cosy;
			xPoints[0] = x2 - (b + c);
			yPoints[0] = y2 - (a - d);
			xPoints[2] = x2 - (b - c);
			yPoints[2] = y2 - (d + a);

			this.polygon([xPoints[0], yPoints[0], xPoints[1], yPoints[1], xPoints[2], yPoints[2]], color, true);
		}

		/**
		 * 显示流程线路 srcs 源节点，坐标大小数组 tos 目标节点，坐标大小数组 trace 线路额外节点坐标数组 srcType 源节点类型
		 * toType 目标节点类型 color 线路颜色
		 */
		this.showLine = function(srcs, tos, trace, srcType, toType, color) {
			var srcRectangle = new Rectang(srcs[0], srcs[1], srcs[2], srcs[3]);
			var toRectangle = new Rectang(tos[0], tos[1], tos[2], tos[3]);
			var traces = new Array();// points
			traces[0] = new Point(srcRectangle.getCenterX(), srcRectangle.getCenterY());
			if (trace && trace.length > 0) {
				for (var j = 0; j < trace.length / 2; j++) {
					traces[j + 1] = new Point(parseInt(trace[j * 2]), parseInt(trace[j * 2 + 1]));
				}
			}
			traces.push(new Point(toRectangle.getCenterX(), toRectangle.getCenterY()));
			var len = traces.length;
			var xPoints = new Array(len);
			var yPoints = new Array(len);
			for (var i = 0; i < len; i++) {
				xPoints[i] = traces[i].x;
				yPoints[i] = traces[i].y;
			}
			var taskGrow = 8;
			var smallGrow = 4;
			var grow = 0;
			if (srcType == 'state' || srcType == 'task') {
				grow = taskGrow;
			} else {
				grow = smallGrow;
			}
			var p = getRectangleLineCrossPoint(srcRectangle, new Point(xPoints[1], yPoints[1]), grow);
			if (p != null) {
				xPoints[0] = p.x;
				yPoints[0] = p.y;
			}
			if (toType == 'state' || toType == 'task') {
				grow = taskGrow;
			} else {
				grow = smallGrow;
			}
			p = getRectangleLineCrossPoint(toRectangle, new Point(xPoints[len - 2], yPoints[len - 2]), grow);
			if (p != null) {
				xPoints[len - 1] = p.x;
				yPoints[len - 1] = p.y;
			}
			var pointA = new Array();
			for (var k = 0; k < xPoints.length; k++) {
				pointA.push(xPoints[k]);
				pointA.push(yPoints[k]);
			}
			this.polygon(pointA, color, false, true);
		}

		/**
		 * 函数功能：根据给定的一系列坐标点画多边形 函数思路：循环读取坐标点，在当前坐标点与下一坐标点之间连线 pointA 点数组 color
		 * 颜色 isClose 是否闭合 isArrow 最后是否有箭头
		 */
		this.polygon = function(pointA, color, isClose, isArrow) {
			var args = pointA.length;
			if (args % 2 != 0)
				return -1;
			var realargs = args / 2; // 坐标个数
			for (var i = 0; i < realargs - 1; i++) {
				if (i == realargs - 2 && isArrow) {
					this.drawLine(pointA[i * 2], pointA[i * 2 + 1], pointA[i * 2 + 2], pointA[i * 2 + 3], color, true);
				} else {
					this.drawLine(pointA[i * 2], pointA[i * 2 + 1], pointA[i * 2 + 2], pointA[i * 2 + 3], color);
				}
			}
			if (isClose) {
				this.drawLine(pointA[i * 2], pointA[i * 2 + 1], pointA[0], pointA[1], color);
			}
		}
	}
	window.Draw2D = draw;

})(jQuery);
